package com.blog.aspect;
import com.blog.annotation.RequiredTime;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * @Aspect描述的类为面向切面编程中的切面定义，切面中要定义的元素：
 * 1)切入点(要织入扩展功能的一些列目标方法的集合，例如商品查询方法，用户登陆方法)
 * 2)通知Advice(调用目标执行链，添加扩展业务逻辑)
 */
@Order(2)
@Aspect
@Component
public class TimeAspect {
    private static Logger log= LoggerFactory.getLogger(TimeAspect.class);
    /**
     * @Pointcut注解用于定义切入点，切入点的内容可以是一些表达式，我们
     * 这个案例中先使用一个注解表达式。后续我们可以使用注解表达式中的
     * 注解描述切入点方法，通过通知方法为切入点方法进行功能拓展。
     */
    @Pointcut("@annotation(com.blog.annotation.RequiredTime)")
    public void doTime(){}//doTime没有实际意义，方法体中不用写内容，只是为了承载注解的定义


    /**
     * @Around注解描述的方法为一个环绕通知方法，此方法内部可以手动调用目标执行链(proceed)，
     * 并且在目标执行链之前或之后添加一些扩展业务逻辑。(例如记录时间、权限校验，事务控制、缓存)
     * @param joinPoint 连接节点-封装了目标切入点方法信息
     * @return
     * @throws Throwable
     */
    @Around("doTime()")//doTime()为@PointCut注解描述的方法,这里表示引用方法上的切入点定义
    public Object doTimeAround(ProceedingJoinPoint joinPoint)throws Throwable{
        log.info("TimeAspect.@Around.Before {}", LocalDateTime.now());
        try {
            Object result = joinPoint.proceed();//调用目标执行链(可以有当前类的其它通知方法+其它切面中的通知方法+目标业务方法)
            log.info("@Around.AfterReturning {}", LocalDateTime.now());
            return result;
        }catch(Throwable e){
            log.info("@Around.AfterException {}", LocalDateTime.now());
            throw e;
        }finally{
            log.info("@Around.After{}", LocalDateTime.now());
        }
    }

    /**
     * @Before通知，此方法会在目标方法执行之前执行
     */
    @Before("doTime()")
    public void doBefore(){
        log.info("@Before");
    }

    /**
     *  @After通知，在目标方法执行之后执行(无论目标方法执行过程是否出现了异常，它都会执行)，
     */
    @After("doTime()")
    public void doAfter(){
        log.info("@After");
    }

    /**
     * @AfterReturning通知在目标方法正确结束后执行。
     */
    @AfterReturning("doTime()")
    public void doAfterReturning(JoinPoint joinpoint){
        log.info("@AfterReturning");
    }
    /**
     * @AfterThrowing通知在目标方法异常结束后执行。
     */
    @AfterThrowing(value="@annotation(requiredTime)",throwing ="ex")
    public void doAfterThrowing(JoinPoint joinPoint, RequiredTime requiredTime,Throwable ex){
        log.info("@AfterThrowing");
    }
}
